package algebra.rewriting.kfree;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import kb.DescriptionLogicsKB;
import kb.TBox;
import reasoner.KBReason;
import tableaux.rules.RuleLazyUnfolding;
import utils.exception.CoreReasoningException;

import entities.concept.Concept;

import algebra.op.*;
import algebra.query.pd.*;
import algebra.rewriting.RewriteRule;

public class KFreeRewriting extends RewriteRule {
	private final AlgebraicOP op;
	private static Set<String> restrictedConstants = new HashSet<String>();
	private static boolean useCache = true;
	private static int cacheHit = 0;
	private static Map<AlgebraicOP, Set<AlgebraicOP>> cachedEqLqs = new HashMap<AlgebraicOP, Set<AlgebraicOP>>();
	private static Map<AlgebraicOP, Set<AlgebraicOP>> nonEqCache = new HashMap<AlgebraicOP, Set<AlgebraicOP>>();

	public static int getCacheHit() {
		return cacheHit;
	}

	private static void insertNoneCacheEntry(AlgebraicOP left, AlgebraicOP right) {
		Set<AlgebraicOP> rs = null;
		if (nonEqCache.containsKey(left)) {
			rs = nonEqCache.get(left);
		} else {
			rs = new HashSet<AlgebraicOP>();
		}
		rs.add(right);
		nonEqCache.put(left, rs);
	}

	private static void insertCacheEntry(AlgebraicOP left, AlgebraicOP right) {
		Set<AlgebraicOP> rs = null;
		if (cachedEqLqs.containsKey(left)) {
			rs = cachedEqLqs.get(left);
		} else {
			rs = new HashSet<AlgebraicOP>();
		}
		rs.add(right);
		cachedEqLqs.put(left, rs);
	}

	private static boolean hasNoneCacheEntry(AlgebraicOP left, AlgebraicOP right) {
		Set<AlgebraicOP> rs = null;
		if (nonEqCache.containsKey(left)) {
			rs = nonEqCache.get(left);
		} else {
			return false;
		}
		return rs.contains(right);
	}

	private static boolean hasCacheEntry(AlgebraicOP left, AlgebraicOP right) {
		Set<AlgebraicOP> rs = null;
		if (cachedEqLqs.containsKey(left)) {
			rs = cachedEqLqs.get(left);
		} else {
			return false;
		}
		return rs.contains(right);
	}

	public static void setConstants(Set<String> input) {
		restrictedConstants.addAll(input);
	}

	public static void clearCacheConstantsAndResetCounter() {
		restrictedConstants.clear();
		cachedEqLqs.clear();
		nonEqCache.clear();
		cacheHit = 0;
	}

	public static Set<String> getCapturedConstants() {
		return restrictedConstants;
	}

	// public static AlgebraicOP getKFreeOP(AlgebraicOP op){
	// if(op instanceof SelectionOP){
	// SelectionOP sp = (SelectionOP) op;
	// // SelectionOP newsp = null;
	// if(KFreeOP(sp)){
	// return sp.getKFreeVersion();
	// // newsp = new SelectionOP(getKFreeOP(sp.getSubquery()),
	// sp.getSelectionCondition(), null);
	// }else{
	// return sp;
	// // newsp.setPlanEvalKB(sp.getPlanEvalKB());
	// }
	// // return newsp;
	// }else if(op instanceof ProjectionOP){
	// ProjectionOP pp = (ProjectionOP) op;
	// if(KFreeOP(pp)){
	// return pp.getKFreeVersion();
	// }else{
	// return pp;
	// }
	// }else if(op instanceof IntersectionOP){
	// IntersectionOP ip = (IntersectionOP) op;
	// IntersectionOP newip = new
	// IntersectionOP(getKFreeOP(ip.getFirstSubQuery()),
	// getKFreeOP(ip.getSecondSubQuery()));
	// return newip;
	// }
	// else if(op instanceof ScanOP){
	// ScanOP scan = (ScanOP) op;
	// ScanOP newscan = new ScanOP(getKFreeOP(scan.getSubquery()),
	// scan.getSecondaryIndex());
	// return newscan;
	// }
	// else{
	// //the same op for other ops
	// return op;
	// }
	// }
	
	Set<Concept> cutSelection(Concept c, Set<Concept> tups){
		Set<Concept> ret = new HashSet<Concept>();
		for(Concept d:tups){
			if(d.isBot()){
				continue;
			}
			//test early
			DescriptionLogicsKB kb = DescriptionLogicsKB.newKBInstance(
					new TBox(), null);
			KBReason res = new KBReason();
			try{
			if (!res.subsumesGuarded(c, d, kb) ){
				System.err.println("prune one");
				continue;
			}
			}catch(Exception e){
				e.printStackTrace();
			}
			ret.add(d);
		}
		return ret;
	}

	public static boolean KFreeOP(SelectionOP op) {
		RuleLazyUnfolding.clearTfFeatureValues();
		AlgebraicOP sub = op.getSubquery();
		Concept c = op.getSelectionCondition();
		if (useCache) {
			if (hasCacheEntry(op, sub)) {
				cacheHit++;
				return true;
			} else if (hasNoneCacheEntry(op, sub)) {
				cacheHit++;
				return false;
			}
		}

		if (sub.isPure()) {
			DescriptionLogicsKB kb = DescriptionLogicsKB.newKBInstance(
					new TBox(), null);
			Set<Concept> lsubq = LangRep.getOPRepConcepts(sub,
					restrictedConstants, kb);
			// for selection, C ~> Lq
			boolean find = false;
			if(lsubq.contains(c)){
				if (useCache) {
					insertCacheEntry(op, sub);
				}
				return true;
			}
			for (Concept d : lsubq) {
				KBReason res = new KBReason();
				try {
					if (!res.subsumesGuarded(c, d, kb) || !res.subsumesGuarded(d, c, kb)) {
						continue;
					}
					find = true;
					break;
				} catch (CoreReasoningException e) {
					e.printStackTrace();
				}
			}
			// cache it!
			if(find){
				if (useCache) {
					insertCacheEntry(op, sub);
				}
				return true;
			}else{
				if (useCache) {
					insertNoneCacheEntry(op, sub);
				}
				return false;
			}
		}
		if (useCache) {
			insertNoneCacheEntry(op, sub);
		}
		return false;
	}

	private static boolean quickProjectionKFreeSyntacticCheck = true;

	public static boolean KFreeOP(ProjectionOP op) {
		RuleLazyUnfolding.clearTfFeatureValues();
		// System.err.println("check if "+op+" can be free..");
		AlgebraicOP sub = op.getSubquery();
		if (useCache) {
			if (hasCacheEntry(op, sub)) {
				cacheHit++;
				return true;
			} else if (hasNoneCacheEntry(op, sub)) {
				cacheHit++;
				return false;
			}
		}
		if (sub.isPure()) {
			// apply trick first
			// suppose Pd is f?, and its subquery is Scan_s1 U Scan_s2 etc., as
			// long as f? appears in any of Si, bingo!
			if (quickProjectionKFreeSyntacticCheck) {
				if (sub.hasOnlyScanOP()) {
					ProjDesc mainpd = op.getPrjDesc();
					for (ScanOP scan : sub.getAllScanOP()) {
						if (scan.getSecondaryIndex().getPdi()
								.moreSpecificThan(mainpd)) {
							if (useCache) {
								insertCacheEntry(op, sub);
							}
							return true;
						}
					}
				}
			}
			DescriptionLogicsKB kb = DescriptionLogicsKB.newKBInstance(
					new TBox(), null);
			boolean looseCond = op.getPrjDesc().projectOnSomeFeature()
					|| projectSomeFeature(sub);
//			Set<Concept> leftset = LangRep.getOPRepConcepts(op, restrictedConstants, kb);
			Set<Concept> leftset = LangPd.getLangPdTup(op.getPrjDesc(), restrictedConstants, kb);
			if (LangRep.EqConceptSet(
					leftset,
					LangRep.getOPRepConcepts(sub, restrictedConstants, kb),
					looseCond, kb)) {
				if (useCache) {
					insertCacheEntry(op, sub);
				}
				return true;
			}
		}
		if (useCache) {
			insertNoneCacheEntry(op, sub);
		}
		return false;
	}

	private static boolean projectSomeFeature(AlgebraicOP q) {
		if (q instanceof ScanOP) {
			return q.asScanOP().getSecondaryIndex().getPdi()
					.projectOnSomeFeature();
		} else if (q instanceof ProjectionOP) {
			return q.asProjectionOP().getPrjDesc().projectOnSomeFeature();
		} else if (q instanceof SelectionOP) {
			return projectSomeFeature(q.asSelectionOP().getSubquery());
		} else if (q instanceof JoinOP) {
			JoinOP ip = q.asIntersectionOP();
			boolean ret = projectSomeFeature(ip.getFirstSubQuery())
					|| projectSomeFeature(ip.getSecondSubQuery());
			return ret;
		} else {
			return false;
		}
	}

	public KFreeRewriting(AlgebraicOP op) {
		this.op = op;
	}

	@Override
	public AlgebraicOP rewrite() {
		if (op instanceof SelectionOP) {
			SelectionOP sp = (SelectionOP) op;
			if (KFreeOP(sp)) {
				return sp.getKFreeVersion();
				// sp.setPlanEvalKB(null);
			}
		} else if (op instanceof ProjectionOP) {
			ProjectionOP pp = (ProjectionOP) op;
			if (KFreeOP(pp)) {
				return pp.getKFreeVersion();
				// pp.setPlanEvalKB(null);
			}
		}
		return null;
	}

	@Override
	public boolean applicable() {
		if (this.op instanceof ProjectionOP) {
			ProjectionOP pp = (ProjectionOP) op;
			return pp.getPlanEvalKB() != null;
		} else if (op instanceof SelectionOP) {
			SelectionOP sp = (SelectionOP) op;
			return sp.getPlanEvalKB() != null;
		}
		return false;
	}

}
